package com.csc775.networking;

import java.io.*;
import java.net.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;

import com.csc775.message.ControlMessage;
import com.csc775.message.ControlRquestType;
import com.csc775.userinfo.UserInfo;
/**
 * This class is mainatned at server side for each new client joining
 *
 */
public class ServerThread extends Thread {
	private Socket TCPsocket = null;// TCP socket
	private Server server = null;// Server object
	private int ID = -1;
	private DataInputStream streamIn = null;// Data input stream for receiving message
	private ObjectOutputStream streamOut = null;// Data output stream for sending message
    private String message;//message to send to the client
	private DatagramSocket UDPsocket;//UDP socket
	UserInfo info; //UserInfo object
	ControlMessage msg;  //ControlMessage object
	private boolean isIPInGroup = true; //checks if the user's Ip is in group

	/**
	 * constructor for ServerThread
	 * @param server
	 * @param socket
	 * @param id
	 * @param soc
	 * @param info
	 */
	public ServerThread(Server server, Socket socket, int id,
			DatagramSocket soc, UserInfo info) {
		this.server = server;
		this.TCPsocket = socket;
		this.ID = id;
		this.UDPsocket = soc;
		this.info = info;
	}

	/**
	 * Constructor for ServerThread
	 * @param server
	 * @param socket
	 * @param id
	 * @param soc
	 */
	public ServerThread(Server server, Socket socket, int id, DatagramSocket soc) {
		this.server = server;
		this.TCPsocket = socket;
		this.ID = id;
		this.UDPsocket = soc;
		UserInfo u = new UserInfo();
		u.setId(ID);

		try {
			u.setIn(new ObjectInputStream(TCPsocket.getInputStream()));
			u.setOut(new ObjectOutputStream(TCPsocket.getOutputStream()));
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		this.info = u;
	}

	@Override
	/**
	 * invoked after serverThread starts and continues to run till the user is connected
	 */
	public void run() {
		System.out.println("Server Thread " + ID + " running.");
		while (true) {
			 //receives message from client and parses the message's fields 
			try {
				message = (String) info.getIn().readObject();                  
				System.out.println("from client>" + message);
				msg = ControlMessage.getControlMessageInstance(message);
               
				//called if the message is a registration message
				if (msg.getRequestType() == ControlRquestType.REGISTER) {
                    //checks if username already exists
					String[] tok = msg.getExtraData().split("~");
					if (Server.getClientInfo().containsKey(tok[0])) {
						generateMsg("INVALID_USER");
					} else {
						//sets the user's information in UserInfo
						info.setUsername(tok[0]);                                 

						info.setPassword(tok[1]);
						info.setIP(msg.getSourceIP());
						info.setActive(true);
						
						// this info needs to be added at list at server.java
						// for each now client gets added in this list
						Server.addClientInfo(info);

						info.print();
						generateMsg("Registration success");
					}
				}
				//called if the message is a login message
				if (msg.getRequestType() == ControlRquestType.LOGIN) {
					String[] tok = msg.getExtraData().split("~");
					if (Server.getClientInfo().containsKey(tok[0])) {
						UserInfo temp = Server.getClientInfoObject(tok[0]);
						if (temp != null) {
							if (temp.getPassword().equals(tok[1])) {
								// user logged in successfully hence
								// he/she is active now
								info.setActive(true);
								for (String str : temp.getGrp())
									info.setGrp(str);
								info.setId(temp.getId());
								info.setIP(msg.getSourceIP());
								info.setPassword(temp.getPassword());
								info.setUsername(temp.getUsername());
								// As info object is create new in the constructor 
								// of this class, we need to feed the values of already
								// existing user information in it.
								// and replace the object in db with this value
								// remove old user information for same user
								Server.getClientInfo().remove(tok[0]);
								// re enter new info of user
								Server.addClientInfo(info);
								generateMsg(null);
							} else {
								info.setUsername(temp.getUsername());
								generateMsg("NOT_VALID_PASSWORD");
							}
						} else {
							generateMsg("INVALID_USER");
						}
					} else {
						generateMsg("INVALID_USER");
					}
				}
				//called when the user sends when hand request is sent to the speaker.
				if(msg.getRequestType()==ControlRquestType.HAND){
						String tok = msg.getExtraData();
						UserInfo temp = Server.getClientInfoObject(Server.talkingUser);
						if(temp!=null){
							ControlMessage newmsg = new ControlMessage(temp.getIP(), 2005, 0, tok);
							newmsg.setRequestType(ControlRquestType.CHANNEL_OPEN);
							temp.getOut().writeObject(newmsg.toString());
						}
				}

				//called if the message is the group joining message that contains 
				// the user group selection
				if (msg.getRequestType() == ControlRquestType.JOIN) {
					StringTokenizer tok = new StringTokenizer(
							msg.getExtraData(), "~");
					while (tok.hasMoreTokens()) {
						info.setGrp(tok.nextToken());
					}
					info.print();
					generateMsg(null);
				}
				
				//called when the user sends the request to talk message
				if (msg.getRequestType() == ControlRquestType.REQUEST_TO_TALK) {
					// group list of connected client who request to talk
					System.out.println(msg.getExtraData());
					Server.talkingUser = msg.getExtraData();
					// extra data has username
					UserInfo tempInfo = Server.getClientInfoObject(msg
							.getExtraData());
					if (tempInfo != null) {
						ArrayList<String> groupList = tempInfo.getGrp();
						System.out
								.println("List of Groups for ==>" + groupList);
						// this will have list of unique address present in that
						// groups
						Set<String> ips = Server.getIPHasGroup(
								msg.getExtraData(), groupList);
						Set<String> usernames = Server.getUserNameList(ips);
						// send message to all these to stop talking

						Iterator<String> itr = ips.iterator();
						StringBuffer buf = new StringBuffer();

						while (itr.hasNext()) {
							String ip = itr.next();
							buf.append(ip);
							buf.append("~");
						}
						System.out.println("IP String ==>" + buf.toString());

						if (buf.length() > 0) {
							itr = usernames.iterator();
							while (itr.hasNext()) {
								String username = itr.next();
								ControlMessage stopTalkMsg = new ControlMessage(
										Server.getClientInfoObject(username)
												.getIP(), 2005, 0,
										buf.toString());
								stopTalkMsg
										.setRequestType(ControlRquestType.REQUEST_TO_LISTEN);
								System.out.println("Sent(Server)>"
										+ stopTalkMsg.toString());
								// This will write on the ouput stream of that
								// object
								Server.getClientInfoObject(username).getOut()
										.writeObject(stopTalkMsg.toString());
							}
							generateMsg(buf.toString());
						} else {
							isIPInGroup = false;
							generateMsg(null);
						}
					} else {
						generateMsg("User " + msg.getExtraData()
								+ " does not exists in the system");
					}

				}
				//called when the user sends leave message
				if (msg.getRequestType() == ControlRquestType.LEAVE) {
					UserInfo temp = Server.getClientInfoObject(msg
							.getExtraData());
					generateMsg("BYE");
					temp.setActive(false);
					temp.getOut().close();
					temp.getIn().close();
					TCPsocket.close();
					Server.displayClientRecords();
					Server.currClientcount--;
					this.stop();
				}

			} catch (EOFException e) {
				System.out.println("Client " + info.getUsername()
						+ " got disconnected");
				// set the data to inavtive in the server DS
				UserInfo temp = Server.getClientInfoObject(info.getUsername());
				if (temp != null) {
					temp.setActive(false);
					try {
						temp.getOut().close();
						temp.getIn().close();
					} catch (IOException e1) {
						// TODO Auto-generated catch block
					}
					Server.displayClientRecords();
				}
				this.stop();
				break;
			} catch (SocketException ex) {

			} catch (Exception e) {
				// System.exit(0);
				System.out.println("Client " + info.getUsername()
						+ " got disconnected");
				// set the data to inavtive in the server DS
				UserInfo temp = Server.getClientInfoObject(info.getUsername());
				if (temp != null) {
					temp.setActive(false);
					try {
						temp.getOut().close();
						temp.getIn().close();
					} catch (IOException e1) {
						// TODO Auto-generated catch block
					}
					Server.displayClientRecords();
				}
				this.stop();
				break;
			}
		}
	}

	/**
	 * Sends & receives UDP packets
	 */

	public void message(Socket t) {
		try {
			PrintWriter out = new PrintWriter(TCPsocket.getOutputStream(), true);
			/* ... and an input stream */
			BufferedReader in = new BufferedReader(new InputStreamReader(
					TCPsocket.getInputStream()));
			/* stdin stream */
			BufferedReader stdIn = new BufferedReader(new InputStreamReader(
					System.in));

			String msg;

			while ((msg = stdIn.readLine()) != null) {
				out.println(msg);
				System.out.println(in.readLine());
			}
		} catch (Exception e) {
			System.out.println(e);
		}
	}

	/**
	 * sends TCP data
	 * 
	 * @param msg
	 */
	void sendMessage(String msg) {

		try {
			info.getOut().writeObject(msg);
			info.getOut().flush();
			System.out.println("Sent(Server): " + msg);
		} catch (IOException ioException) {
			ioException.printStackTrace();
		}
	}

	/**
	 * Generates server response based on the received message from the client
	 * @param buffer
	 */
	public void generateMsg(String buffer) {
		//called when the user wants to register
		if (msg.getRequestType() == ControlRquestType.REGISTER) {
			if (buffer.equals("INVALID_USER")) {
				msg = new ControlMessage(info.getIP(), info.getPort(),
						buffer.length(), buffer.toString());
				msg.setRequestType(ControlRquestType.REGISTER_ACK);

			} else {
				StringBuffer buf = new StringBuffer();
				buf.append("Group:");
				buf.append("Group1");
				buf.append("~");
				buf.append("Group2");
				buf.append("~");
				buf.append("Group3");
				buf.append("~");
				buf.append("Group4");
				msg = new ControlMessage(info.getIP(), info.getPort(),
						buf.length(), buf.toString());
				msg.setRequestType(ControlRquestType.REGISTER_ACK);
			}
		}
		//called when the user wants to join a  group
		if (msg.getRequestType() == ControlRquestType.JOIN) {
			msg = new ControlMessage(info.getIP(), info.getPort(), 0,
					"REG_SUCEESS");
			msg.setRequestType(ControlRquestType.JOIN_ACK);
		}
		if (msg.getRequestType() == ControlRquestType.LOGIN) {
			String s = null;
			if (buffer == null)
				s = "SUCCESS";
			else
				s = buffer;
			msg = new ControlMessage(info.getIP(), info.getPort(), s.length(),
					s);
			msg.setRequestType(ControlRquestType.LOGIN_ACK);
			// sendMessage(msg.toString());
		}
		if (msg.getRequestType() == ControlRquestType.REQUEST_TO_TALK) {
			if (isIPInGroup) {
				msg = new ControlMessage(info.getIP(), info.getPort(), 0,
						buffer);
			} else {
				String str = "NO_IP_IN_GROUP";
				msg = new ControlMessage(info.getIP(), info.getPort(),
						str.length(), str);
				isIPInGroup = true;
			}
			msg.setRequestType(ControlRquestType.REQUEST_TO_TALK_ACK);
		}
		if (msg.getRequestType() == ControlRquestType.LEAVE) {
			msg = new ControlMessage(info.getIP(), info.getPort(),
					buffer.length(), buffer);
			msg.setRequestType(ControlRquestType.LEAVE_ACK);
		}
		msg.getControlPacket().setSourceIP(msg.getServerIp());
		sendMessage(msg.toString());

	}

	/**
	 * closes the socket
	 * @throws IOException
	 */
	public void close() throws IOException {
		if (TCPsocket != null)
			TCPsocket.close();
		if (streamIn != null)
			streamIn.close();
	}
}
